package com.stillalive.mq.mqclient;

import com.stillalive.mq.common.*;
import com.stillalive.mq.common.parameterPassing.SubScribeReturns;

import java.io.*;
import java.net.Socket;
import java.net.SocketException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Connection {
    private Socket socket = null;
    // 使用哈希表组织若干个channel
    private ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>();

    private InputStream inputStream;
    private OutputStream outputStream;
    private DataInputStream dataInputStream;
    private DataOutputStream dataOutputStream;

    private ExecutorService callbackPool = null;

    public Connection(String host, int port) throws IOException {
        socket = new  Socket(host, port);
        inputStream = socket.getInputStream();
        outputStream = socket.getOutputStream();
        dataInputStream = new DataInputStream(inputStream);
        dataOutputStream = new DataOutputStream(outputStream);
        callbackPool =  Executors.newFixedThreadPool(4);
        // 扫描线程，不停从socket中读取响应数据并将其交给对应的channel处理
        Thread t = new Thread(()->{
            try{
                while(!socket.isClosed()){
                    Response response = readResponse();
                    dipathResponse(response);
                }
            }catch(SocketException e){
                System.out.println("[Connection] 连接正常断开！");
            }catch(IOException | ClassNotFoundException | MqException e){
                System.out.println("[Connection] 连接异常断开！");
                e.printStackTrace();
            }
        });
        t.start();
    }

    // 使用这个方法来区分当前响应是针对控制请求的响应还是服务器推送的消息
    private void dipathResponse(Response response) throws IOException, ClassNotFoundException, MqException {
        if(response.getType() == 0xc){
            // 服务器推送来的消息
            SubScribeReturns subScribeReturns = (SubScribeReturns) BinaryTool.fromBytes(response.getPayload());
            // 根据channelId找到对应的channel对象
            Channel channel = channelMap.get(subScribeReturns.getChannelId());
            if(channel == null){
                throw new MqException("[Connection] 该消息对应的channel在客户端不存在！channelId=" + channel.getChannelId());
            }
            // 执行channel对象
            callbackPool.submit(()->{
                try {
                    channel.getConsumer().handleDelivery(subScribeReturns.getConsumerTag(), subScribeReturns.getBasicProperties(), subScribeReturns.getBody());
                } catch (MqException | IOException e) {
                    throw new RuntimeException(e);
                }
            });
        }else {
            // 针对刚才控制请求的响应
            BasicReturns basicReturns = (BasicReturns) BinaryTool.fromBytes(response.getPayload());
            Channel channel = channelMap.get(basicReturns.getChannelId());
            if(channel == null){
                throw new MqException("[Connection] 该消息对应的channel在客户端不存在！channelId=" + channel.getChannelId());
            }
            channel.putReturns(basicReturns);
        }
    }

    // 关闭所有请求
    public void close() {
        try{
            callbackPool.shutdown();
            channelMap.clear();
            inputStream.close();
            outputStream.close();
            socket.close();
        }catch(IOException e){
            e.printStackTrace();
        }
    }
    // 发送请求
    public void writeRequest(Request request) throws IOException {
        dataOutputStream.writeInt(request.getType());
        dataOutputStream.writeInt(request.getLength());
        dataOutputStream.write(request.getPayload());
        dataOutputStream.flush();
        System.out.println("[Connection] 发送请求！type=" + request.getType() + ",length=" + request.getLength());
    }
    // 读取响应
    public Response readResponse() throws IOException {
        Response response = new Response();
        response.setType(dataInputStream.readInt());
        response.setLength(dataInputStream.readInt());
        byte[] payload = new byte[response.getLength()];
        int n = dataInputStream.read(payload);
        if(n != response.getLength()){
            throw new IOException("读取的响应数据不完整！");
        }
        response.setPayload(payload);
        System.out.println("[Connection] 收到响应！type=" +  response.getType() + ",length=" + response.getLength());
        return response;
    }
    public Channel createChannel() throws IOException {
        String channelId = "C-" + UUID.randomUUID().toString();
        Channel channel = new Channel(channelId, this);
        // 把这个channel对象放到Connectin管理的channel的哈希表中
        channelMap.put(channelId, channel);
        // 把创建Channel这个消息告诉给服务器
        boolean ok = channel.createChannel();
        if(!ok){
            // 服务器创建失败了，整个这次创建Channel操作不顺利
            // 需要将刚才已经加入hash表的键值对删除
            channelMap.remove(channelId);
            return null;
        }
        return channel;
    }
}
